package co.baiku.boot.im.tencent;

import co.baiku.boot.AjavaerCache;
import co.baiku.boot.cache.service.CacheService;
import co.baiku.boot.common.tools.JsonTools;
import co.baiku.boot.common.tools.StringTools;
import co.baiku.boot.config.TencentImConfig;
import co.baiku.boot.im.tencent.exception.TencentImException;
import co.baiku.boot.im.tencent.request.TencentImRequest;
import co.baiku.boot.im.tencent.request.TentcentImAPI;
import co.baiku.boot.im.tencent.request.data.*;
import co.baiku.boot.im.tencent.response.*;
import com.tls.tls_sigature.tls_sigature.GenTLSSignatureResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.tls.tls_sigature.tls_sigature.GenTLSSignatureEx;

/**
 * 腾讯云通信
 *
 * @see <a href='https://cloud.tencent.com/document/product/269'>官方文档</a>
 */
@Component
@ConditionalOnProperty("ajavaer.im.tencent.private-key")
public class TencentIm implements InitializingBean {
    private Logger log = LoggerFactory.getLogger(TencentIm.class);
    private Boolean isDebug = true;
    @Resource
    private RestTemplate restTemplate;

    @Resource
    private TencentImConfig tencentImConfig;
    @Resource
    private Environment environment;

    /**
     * 用户登录秘钥生成
     *
     * @param identifier
     * @return
     */
    public String userSig(String identifier) {
        String key = StringTools.format("{0}:{1}", tencentImConfig.getUserSigCacheKey(), identifier);
        CacheService redis = AjavaerCache.redis();
        String sig = redis.get(key);
        if (sig == null) {
            GenTLSSignatureResult signatureResult = GenTLSSignatureEx(tencentImConfig.getAppid(), identifier, tencentImConfig.getPrivateKey(), tencentImConfig.getUserSigExpire());
            if (StringTools.isNotBlank(signatureResult.urlSig)) {
                sig = signatureResult.urlSig;
                redis.put(key, sig, signatureResult.expireTime - 60, TimeUnit.SECONDS);
            } else {
                log.error("签名计算出错:" + signatureResult.errMessage);
            }
        }
        return sig;
    }

    /**
     * 单个帐号导入接口
     * <p>
     * 本接口适用于将开发者自有帐号导入云通信 IM。该接口的功能是在云通信 IM 创建一个内部 ID，使没有登录云通信 IM 的 App 自有帐号能够使用云通信 IM 服务。
     * </p>
     *
     * @param accountImportData 用户信息
     * @see <a href='https://cloud.tencent.com/document/product/269/1608'>官方文档</a>
     */
    public boolean accountImport(AccountImportData accountImportData) {
        return execute(adminRequest(TentcentImAPI.AccountImport, accountImportData), TencentImResponse.class).success();
    }


    /**
     * 批量帐号导入接口
     * <p>
     * 本接口适用于将开发者自有帐号批量导入云通信 IM。该接口的功能是在云通信 IM 创建一个内部 ID，使没有登录云通信 IM 的 App 自有帐号能够使用云通信 IM 服务。
     * </p>
     *
     * @param multiAccountImportData 账号列表
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/4919'>官方文档</a>
     */
    public List<String> multiAccountImport(MultiAccountImportData multiAccountImportData) {
        List<String> accounts = multiAccountImportData.getAccounts();
        MultiAccountImportResponse response = new MultiAccountImportResponse();
        if (accounts.size() <= 100) {//账号列表小于等于100个
            TencentImRequest request = adminRequest(TentcentImAPI.MultiAccountImport, multiAccountImportData);
            response = execute(request, MultiAccountImportResponse.class);
        } else {//分批次请求
            MultiAccountImportData temp = new MultiAccountImportData();
            for (String account : accounts) {
                temp.add(account);
                if (temp.size() == 100) {
                    MultiAccountImportResponse item = execute(adminRequest(TentcentImAPI.MultiAccountImport, temp), MultiAccountImportResponse.class);
                    response.getFailAccounts().addAll(item.getFailAccounts());
                    temp.clear();
                }
            }
            if (temp.size() > 0) {
                MultiAccountImportResponse item = execute(adminRequest(TentcentImAPI.MultiAccountImport, temp), MultiAccountImportResponse.class);
                response.getFailAccounts().addAll(item.getFailAccounts());
            }
        }
        return response.getFailAccounts();
    }

    /**
     * 账号删除接口
     * <p>
     * <li>仅支持删除体验版帐号。</li>
     * <li>帐号删除时，该用户的关系链、资料等数据也会被删除。</li>
     * <li>帐号删除后，该用户的数据将无法恢复，请谨慎使用该接口。</li>
     * </p>
     *
     * @param accountDeleteData 用户信息
     * @see <a href='https://cloud.tencent.com/document/product/269/36443'>官方文档</a>
     */
    public boolean accountDelete(AccountDeleteData accountDeleteData) {
        return execute(adminRequest(TentcentImAPI.AccountDelete, accountDeleteData), TencentImResponse.class).success();
    }

    /**
     * 帐号登录态失效接口
     * <p>
     * 本接口适用于将 App 用户帐号的登录态（如 UserSig）失效。
     * </p>
     *
     * @param kickData [Y] 准备失效的账号
     * @return true 操作成功
     * @see <a href='https://cloud.tencent.com/document/product/269/3853'>官方文档</a>
     */
    public boolean kick(KickData kickData) {
        return execute(adminRequest(TentcentImAPI.Kick, kickData), TencentImResponse.class).success();
    }

    /**
     * 获取 App 中的所有群组
     * <p>
     * App 管理员可以通过该接口获取 App 中所有群组的 ID。
     * </p>
     *
     * @param groupListData [N] 群组查询条件
     * @return {@link GroupListResponse}
     * @see <a href='https://cloud.tencent.com/document/product/269/1614'>官方文档</a>
     */
    public GroupListResponse groupList(GroupListData groupListData) {
        if (groupListData == null) {
            groupListData = new GroupListData();
        }
        return execute(adminRequest(TentcentImAPI.GroupList, groupListData), GroupListResponse.class);
    }

    /**
     * 创建群组
     * <p>
     * App 管理员可以通过该接口创建群组。
     * </p>
     * <h2>注意事项:</h2>
     * <ul>
     * <li>如果指定群组类型为音视频聊天室（AVChatRoom），那么可以创建的群组数量请参考 <a href='https://cloud.tencent.com/document/product/269/11673#.E5.9F.BA.E7.A1.80.E5.A5.97.E9.A4.90.E5.8C.85.EF.BC.88.E9.A2.84.E4.BB.98.E8.B4.B9.EF.BC.89'>价格说明</a>。</li>
     * <li>如果指定群组类型为在线成员广播大群（BChatRoom），那么可以创建的数量为5个，详情请参考 <a href='https://cloud.tencent.com/document/product/269/1502#.E7.BE.A4.E7.BB.84.E9.99.90.E5.88.B6.E5.B7.AE.E5.BC.82'>群组限制差异</a>。</li>
     * <li>如果指定群组类型为私有群（Private）、公开群（Public）或聊天室（ChatRoom）：
     * <ul>
     * <li>请求时如果没有指定群主也没有设置群成员列表，那么创建群数量没有限制。但如果 App 累计总群组数量超过10万，需要支付一定费用。详情请参考 <a href='https://cloud.tencent.com/document/product/269/11673#.E5.90.8E.E4.BB.98.E8.B4.B9.E8.B5.84.E6.BA.90.E6.B6.88.E8.80.97(.E4.B8.8D.E4.BD.BF.E7.94.A8.E4.B8.8D.E8.AE.A1.E8.B4.B9)'>价格说明</a>。</li>
     * <li>请求时如果指定了群主或者设置了群成员列表，那么指定的群主或者群成员，会自动加入到该群中。因为普通账号可以加入的群组数量是有限制，所以此时可以创建的群数量会受到一定限制，详情请参考 <a href='https://cloud.tencent.com/document/product/269/11673#.E5.9F.BA.E7.A1.80.E5.A5.97.E9.A4.90.E5.8C.85.EF.BC.88.E9.A2.84.E4.BB.98.E8.B4.B9.EF.BC.89'>价格说明</a>。</li>
     * </ul>
     *
     * </li>
     * <li>创建音视频聊天室（AVChatRoom）和在线成员广播大群（BChatRoom）时不能拉人入群，如果创建时指定了拉入的群成员会返回10007错误；用户加入音视频聊天室和在线成员广播大群的唯一方式是用户申请加群。</li>
     * </ul>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1615'>官方文档</a>
     */
    public GroupIdResponse createGroup(CreateGroupData createGroupData) {
        return execute(adminRequest(TentcentImAPI.CreateGroup, createGroupData), GroupIdResponse.class);
    }

    /**
     * 获取群组详细资料
     * <p>
     * App 管理员可以根据群组 ID 获取群组的详细信息。
     * </p>
     *
     * @param getGroupInfoData 获取群组信息
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1616'>官方文档</a>
     */
    public GroupInfoResponse groupInfo(GetGroupInfoData getGroupInfoData) {
        return execute(adminRequest(TentcentImAPI.GroupInfo, getGroupInfoData), GroupInfoResponse.class);
    }

    /**
     * 获取群组成员详细资料
     * <p>
     * App 管理员可以根据群组 ID 获取群组成员的资料。
     * </p>
     *
     * @param getGroupMemberInfoData
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1617'>官方文档</a>
     */
    public GroupInfoResponse groupMemberInfo(GetGroupMemberInfoData getGroupMemberInfoData) {
        return execute(adminRequest(TentcentImAPI.GroupMemberInfo, getGroupMemberInfoData), GroupInfoResponse.class);
    }

    /**
     * 修改群组基础资料
     * <p>
     * App 管理员可以通过该接口修改指定群组的基础信息。
     * </p>
     *
     * @param modifyGroupBaseInfoData
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1617'>官方文档</a>
     */
    public GroupInfoResponse modifyGroupInfo(ModifyGroupBaseInfoData modifyGroupBaseInfoData) {
        return execute(adminRequest(TentcentImAPI.ModifyGroupInfo, modifyGroupBaseInfoData), GroupInfoResponse.class);
    }

    /**
     * 添加群组成员
     * <p>
     * App 管理员可以通过该接口向指定的群中添加新成员。
     * </p>
     * <h2>注意</h2>
     * <p>音视频聊天室和在线成员广播大群不支持增加群成员，对这两种类型的群组进行操作时会返回 10007 错误。用户加入这两种群组的唯一方式是用户申请加群。</p>
     *
     * @param addGroupMemberData
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1621'>官方文档</a>
     */
    public AddGroupMemberResponse addGroupMember(AddGroupMemberData addGroupMemberData) {
        return execute(adminRequest(TentcentImAPI.AddGroupMember, addGroupMemberData), AddGroupMemberResponse.class);
    }

    /**
     * 删除群组成员
     * <p>
     * App 管理员可以通过该接口删除群成员。
     * <h2>注意</h2>
     * <ul>
     * <li>音视频聊天室不支持删除群成员，对这种类型的群组进行操作时会返回10004错误。如果管理员希望达到删除群成员的效果，可以通过设置 <a href='https://cloud.tencent.com/document/product/269/1627'>批量禁言和取消禁言</a>的方式实现；</li>
     * <li>在线成员广播大群也不支持删除群成员，对这种类型的群组进行操作时同样会返回10004错误。且该类型群组只支持 App 管理员推送广播消息。</li>
     * </ul>
     * </p>
     *
     * @param addGroupMemberData
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1622'>官方文档</a>
     */
    public TencentImResponse deleteGroupMember(AddGroupMemberData addGroupMemberData) {
        return execute(adminRequest(TentcentImAPI.AddGroupMember, addGroupMemberData), TencentImResponse.class);
    }

    /**
     * 修改群成员资料
     * <p>
     * App 管理员可以通过该接口修改群成员资料。
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1623'>官方文档</a>
     */
    public TencentImResponse modifyGroupMemberInfo() {
        //FIXME 待实现
        return null;
    }

    /**
     * 解散群组
     * <p>
     * App 管理员通过该接口解散群。
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1624'>官方文档</a>
     */
    public TencentImResponse destroyGroup(GroupIdData groupIdData) {
        return execute(adminRequest(TentcentImAPI.DestroyGroup, groupIdData), TencentImResponse.class);
    }

    /**
     * 获取用户所加入的群组
     * <p>
     * App 管理员可以通过该接口获取某一用户加入的群组。
     * <h2>注意</h2>
     * 音视频聊天室和在线成员广播大群因为内部实现的差异，获取用户加入的群组时不会获取到这两种类型的群组。
     *
     * </p>
     *
     * @see <a href='https://cloud.tencent.com/document/product/269/1625'>官方文档</a>
     */
    public GroupListResponse getJoinedGroupList(GetJoinedGroupListData getJoinedGroupListData) {
        return execute(adminRequest(TentcentImAPI.GetJoinedGroupList, getJoinedGroupListData), GroupListResponse.class);
    }

    /**
     * 查询用户在群组中的身份
     * <p>
     * App 管理员可以通过该接口获取一批用户在群内的身份。
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1626'>官方文档</a>
     */
    public TencentImResponse getRoleInGroup() {
        //FIXME 待实现
        return null;
    }

    /**
     * 批量禁言和取消禁言
     * <ul>
     * <li>App 管理员禁止指定群组中某些用户在一段时间内发言。</li>
     * <li>App 管理员取消对某些用户的禁言。</li>
     * <li>被禁言用户退出群组之后再进入同一群组，禁言仍然有效。</li>
     * </ul>
     * <p>
     * 支持的群类型:公开群（Public）,聊天室（ChatRoom）,音视频聊天室（AVChatRoom）
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1627'>官方文档</a>
     */
    public TencentImResponse forbidSendMsg(ForbidSendMsgData forbidSendMsgData) {
        return execute(adminRequest(TentcentImAPI.ForbidSendMsg, forbidSendMsgData), TencentImResponse.class);
    }

    /**
     * 获取群组被禁言用户列表
     * <p>
     * App 管理员可以根据群组 ID 获取群组中被禁言的用户列表。
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/2925'>官方文档</a>
     */
    public GroupShuttedUinResponse getGroupShuttedUin(String groupId) {
        return execute(adminRequest(TentcentImAPI.GetGroupShuttedUin, GroupIdData.of(groupId)), GroupShuttedUinResponse.class);
    }

    /**
     * 在群组中发送普通消息
     * <p>
     * App 管理员可以通过该接口在群组中发送普通消息。
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1629'>官方文档</a>
     */
    public SendMsgResponse sendGroupMsg(SendGroupMsgData sendGroupMsgData) {
        return execute(adminRequest(TentcentImAPI.SendGroupMsg, sendGroupMsgData), SendMsgResponse.class);
    }

    /**
     * 在群组中发送系统通知
     * <p>
     * App 管理员可以通过该接口在群组中发送系统通知。
     * </p>
     * <p>
     * 注意:<br/>
     * 私有群、公开群、聊天室支持向群组中的一部分指定成员发送系统通知，而音视频聊天室和在线成员广播大群只支持向群组中所有成员发送系统通知。
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/1630'>官方文档</a>
     */
    public TencentImResponse sendGroupSystemNotification(SendGroupSystemNotificationData sendGroupSystemNotificationData) {
        return execute(adminRequest(TentcentImAPI.SendGroupSystemNotification, sendGroupSystemNotificationData), TencentImResponse.class);
    }

    /**
     * 群组消息撤回
     * <p>
     * App 管理员通过该接口撤回指定群组的消息，消息需要在漫游有效期以内。
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/12341'>官方文档</a>
     */
    public TencentImResponse groupMsgRecall() {
        //FIXME 待实现
        return null;
    }

    /**
     * 查询 App 自定义脏字
     * <p>
     * App 管理员可以通过该接口查询已设置的自定义脏字。
     * </p>
     * <h2>背景说明</h2>
     * <li>脏字指云通信 IM 为 App 配置的敏感词，如果检查的内容包含脏字将拒绝请求，并返回错误码 30005、40005、80001。</li>
     * <li>脏字检查的内容包括单聊和群组消息（只检查文本消息 TIMTextElem，不支持对自定义消息 TIMCustomElem 的过滤）、群名片、群组资料（群名称、群简介、群公告）用户资料和好友关系链中 bytes 类型的数据（如昵称、好友备注和好友分组等）。</li>
     * <li>云通信 IM 的脏字集已经涵盖了一批默认脏字（主要是政治、色情等领域）。</li>
     * <li>如果 App 有除默认脏字集之外的自定义脏字需求，可以通过脏字管理的 REST API 进行配置。</li>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/2396'>官方文档</a>
     */
    public DirtyWordsResponse getOpenimDirtyWords() {
        return executeGET(adminRequest(TentcentImAPI.GetOpenimDirtyWords, null), DirtyWordsResponse.class);
    }

    /**
     * 添加 App 自定义脏字
     * <h3>功能说明</h3>
     * <ul>
     * <li>App 管理员可以通过该接口添加自定义的脏字。</li>
     * <li>添加自定义脏字成功后，需要等待1小时才能生效。</li>
     * <li>单次请求添加脏字不能超过50个，如果脏字数量超过50个，需要分多次请求。累计添加的脏字，建议不要超过5000个。</li>
     * <li>脏字生效后，包含该脏字的请求都会返回失败，所以请务必确保脏字是经过严格谨慎的筛选的。</li>
     * </p>
     * <h3>建议</h3>
     * <ul>
     * <li>不要把常用且含有简单的词语加为脏字，比如单个字母、数字或汉字等，否则会出现大量消息发送失败。</li>
     * <li>不要把整句的长文本添加为脏字，这样的打击效果很差，建议筛选长文本中的关键词语添加。</li>
     * </ul>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/2397'>官方文档</a>
     */
    public TencentImResponse addOpenimDirtyWords(DirtyWordsData dirtyWordsData) {
        return openimDirtyWords(dirtyWordsData, TentcentImAPI.AddOpenimDirtyWords);
    }

    /**
     * 删除 App 自定义脏字
     * <p>
     * App 管理员可以通过该接口删除自定义的脏字
     * </p>
     *
     * @return
     * @see <a href='https://cloud.tencent.com/document/product/269/2398'>官方文档</a>
     */
    public TencentImResponse deleteOpenimDirtyWords(DirtyWordsData dirtyWordsData) {
        return openimDirtyWords(dirtyWordsData, TentcentImAPI.DeleteOpenimDirtyWords);
    }

    private TencentImResponse openimDirtyWords(DirtyWordsData dirtyWordsData, TentcentImAPI api) {
        List<String> dirtyWordsList = dirtyWordsData.getDirtyWordsList();
        TencentImResponse response = null;
        if (dirtyWordsList.size() <= 50) {//账号列表小于等于50个
            response = execute(adminRequest(api, dirtyWordsData), TencentImResponse.class);
        } else {//分批次请求
            DirtyWordsData temp = new DirtyWordsData();
            for (String account : dirtyWordsList) {
                temp.add(account);
                if (temp.size() == 50) {
                    response = execute(adminRequest(api, temp), TencentImResponse.class);
                    temp.clear();
                }
            }
            if (temp.size() > 0) {
                response = execute(adminRequest(api, temp), TencentImResponse.class);
            }
        }
        return response;
    }

    /**
     * 管理员请求
     *
     * @param api  api接口
     * @param data 要发送的数据
     * @return
     */
    private TencentImRequest adminRequest(TentcentImAPI api, TencentImRequestData data) {
        TencentImRequest request = new TencentImRequest(tencentImConfig.getAppid(), api, tencentImConfig.getAdmin(), data);
        String userSig = userSig(tencentImConfig.getAdmin());
        if (StringTools.isBlank(userSig)) {
            throw new TencentImException("签名计算出错");
        }
        request.setUsersig(userSig);
        return request;
    }

    /**
     * 执行请求,集中异常处理
     *
     * @param request
     * @param <T>
     * @return
     */
    private <T extends TencentImResponse> T execute(TencentImRequest request, HttpMethod method, Class<T> clazz) {
        ResponseEntity<T> response = doExchange(request, method, clazz);
        if (response.getStatusCodeValue() == 200) {
            return response.getBody();
        } else {
            log.error("API请求出错,request:" + JsonTools.beanToJson(request) + ",response:" + response.getStatusCode());
            throw new TencentImException(response);
        }
    }

    private <T extends TencentImResponse> T execute(TencentImRequest request, Class<T> clazz) {
        return execute(request, HttpMethod.POST, clazz);
    }

    private <T extends TencentImResponse> T executeGET(TencentImRequest request, Class<T> clazz) {
        return execute(request, HttpMethod.GET, clazz);
    }

    /**
     * 集中请求处理
     *
     * @param request
     * @param <T>
     * @return
     */
    private <T extends TencentImResponse> ResponseEntity<T> doExchange(TencentImRequest request, HttpMethod method, Class<T> clazz) {
        String url = request.getHost() + request.getApi().getApi() + request.queryString();
        if (isDebug) {
            log.debug("[Im Tencent] doExchange request:" + JsonTools.beanToJson(request));
        }
        return restTemplate.exchange(url, method, new HttpEntity<>(request.getData()), clazz);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
        this.isDebug = this.environment.getProperty("ajavaer.debug", Boolean.class, true);
        if (isDebug) {
            log.debug("Im Tencent init");
        }
    }
}
